<?php
Cinnamon::loadClass('UIComponent');
Cinnamon::loadClass('DataGridColumn');

/**
 * Data Grid class, which extends the DataGridFragment class
 */
class DataGrid extends UIComponent {
	/**
	 * An array of columns
	 */
	protected $columns;
	
	/**
	 * The data provider for this instance
	 */
	protected $dataProvider;
	
	/**
	 * Default constructor
	 */
	public function __construct($title=null) {
		parent::__construct();
		
		// initialize variables
		$this->columns = array();
		$this->dataProvider = null;
		
		// register properties
		$this->registerProperties('headers', 'zebra', 'pagination', 'page', 'pages', 'perpage');
		
		// initialize properties
		$this->title 		= $title;
		$this->headers		= true;
		$this->zebra		= true;
		$this->pagination 	= false;
		$this->page 		= 1;
		$this->pages 		= -1;
		$this->perpage 		= 10;
		$this->renderer		= '/view/datagrid.php';
	}
	
	public function __clone() {
		foreach ( $this->columns as $k=>$column ) {
			$column = clone $column;
			$column->setParent($this);
			$this->columns[$k] = $column;
		}
	}
	
	/**
	 * Appends a column to this fragment. There are two ways to call this function
	 * 1. $fragment->addColumn( new DataGridColumn('your title here') );
	 * 2. $fragment->addColumn('your title here', 'property.one', 'its value', 'property.two', 'another value')
	 * The second way is actually a shorcut for creating an instance of Data Grid Column and changing its columns
	 *
	 * @param mixed column the column to be appended, either an instance of DataGridColumn, or a column title
	 */
	public function addColumn($column) {
		if ( !is_object($column) ) {
			$column = new DataGridColumn($column);
		
			$properties = func_get_args();
			array_shift($properties);
			$column->setProperties($properties);
		}
		
		array_push($this->columns, $column);
		$column->setParent($this);
	}
	
	/**
	 * Inserts the specified column at the specified position. In addition, shifts the column currently at that position (if any) and any subsequent columns.
	 * 
	 * @param object fragment the column to be inserted. Please see addColumn parameters for more details
	 * @param integer index index at which the specified column is to be inserted
	 */
	public function insertColumnAt($index, $column) {
		if ( !is_object($column) ) {
			$column = new DataGridColumn($column);
		
			$properties = func_get_args();
			array_shift($properties);
			array_shift($properties);
			$column->setProperties($properties);
		}
		
		array_insert_at($this->columns, $index, $column);
		$column->setParent($this);
	}
	
	/**
	 * Returns the column at the specified position
	 *
	 * @param integer index index of column to return
	 * @return object the column at the specified position
	 */
	public function getColumnAt($id) {
		return $this->columns[$id];
	}
	
	/**
	 * Returns an array of the columns of this fragment
	 *
	 * @return array an array of the columns of this fragment
	 */	
	public function getAllColumns() {
		return $this->columns;
	}
	
	/**
	 * Returns the number of columns of this fragment
	 * 
	 * @return integer the number of columns of this fragment
	 */	
	public function columnsCount() {
		return count($this->columns);
	}
	
	/**
	 * Removes the column at the specified position. In addition, shifts the column currently at that position (if any) and any subsequent columns.
	 *
	 * @param integer index the position of the internal fragment to remove
	 */	
	public function removeColumnAt($index) {
		$this->getColumnAt($index)->setParent($this);
		$this->columns = array_key_remove($this->columns, $index);	
	}
	
	/**
	 * Removes all the columns from this fragment
	 */	
	public function removeAllColumns() {
		$this->columns = array();
	}	
	
	/**
	 * Assigns a new data provider for this fragment (see Data Provider interface)
	 *
	 * @param object dataProvider the new data provider
	 */
	public function setDataProvider($dataProvider) {
		$this->dataProvider = $dataProvider;
	}
	
	/**
	 * Returns the assigned data provider for this fragment
	 *
	 * @return object the assigned data provider for this fragment
	 */
	public function getDataProvider() {
		return $this->dataProvider;
	}
	
	/**
	 * Returns the corresponding cell value at the specified row and column. 
	 *
	 * @param mixed item the row to be queried
	 * @param integer column the column id to be queried
	 */
	public function getValueAt($item, $columnId) {
		$column = $this->getColumnAt($columnId);
	
		// we have to take care of some properties here...
		$cellValue = null;
		
		// 1. Label field: if specified, retrieve the value from the data provider
		if ( $column->field !== false ) {
			$field = $column->field;
			$cellValue = ( is_object($item) ) ? $item->{$field} : $item[$field];
		}
		
		// 2. Label function: if specified, call the function
		if ( $column->function ) {
			$function = $column->function;
			if ( is_callable($function) ) {
				$cellValue = call_user_func($function, $item);
			}
		}
		
		// 3. Label format: if specified, apply the requested format
		// otherwise try to 'guess' the format :)
		if ( $column->format ) {
			if ( $column->format != 'none' ) {	
				$cellValue = Cinnamon::registry('formats')->format($cellValue, $column->format);
			}
		} else {
			if ( is_datetime($cellValue) ) {
				$cellValue = Cinnamon::registry('formats')->format($cellValue, 'datetime');
			} else if ( is_numeric($cellValue) ) {
				$cellValue = Cinnamon::registry('formats')->format($cellValue, 'number');
			} else if ( is_percentage($cellValue) ) {
				$cellValue = Cinnamon::registry('formats')->format($cellValue, 'percentage');
			}
		}
		
		return $cellValue;
	}
	
	//
	// Other functions
	//
	protected function isNumeric($value) {
		// numbers
		if ( is_numeric($value) ) {
			return true;
		}
		
		// percentage
		if ( preg_match('/^[0-9]+(\.[0-9]+)?%$/', $value) ) {
			return true;
		}
		
		// version
		if ( preg_match('/^[0-9]+(\.[0-9]+)*$/', $value) ) {
			return true;
		}		
		
		// metrics 
		if ( preg_match('/^[0-9]+(\.[0-9]+)?\s[A-Za-z]+$/', $value) ) {
			return true;
		}
		
		return false;
	}
		
	/**
	 * Renders this data grid
	 */
	public function render() {
		//$this->dispatchRenderEvent();
	
		$params = Cinnamon::registry('params');
		
		// take care of pagination
		if ( $this->pagination ) {
			// 1. replace active page in case of an ajax request
			if ( Cinnamon::inMode('ajax') && isset($params->page) ) {
				$this->page = intval($params->page);
			} 
			
			// 2. calculate the number of available pages and fix any possible error
			$limit = $this->getDataProvider()->size();
			$limit = intval($limit) / $this->perpage;
			$limit = intval( ceil($limit) );
			//$this->pages = min($limit, $this->pages);
			$this->pages = ( $this->pages < 0 ) ? $limit : min($limit, $this->pages);
			unset($limit);
			
			// 3. fix any possible errors with pages.limit
			if ( !is_int($this->pages) || $this->pages <= 0 ) {
				$this->pagination = false;
			}
		
			// 4. fix any possible errors with pages.active
			if ( !is_int($this->page) || $this->page <= 0 ) {
				$this->page = 1;
			} 
			$this->page = min($this->page, $this->pages);
			
			// 5. apply pagination rules
			if ( $this->pagination ) {
				$offset = ( $this->page - 1 ) * $this->perpage;
				$this->getDataProvider()->slice($offset, $this->perpage);
			}			
		}
		
		// check for ajax requests
		if ( Cinnamon::inMode('ajax') ) {
			if ( strcmp($this->id, $params->component) == 0 ) {
				parent::render();
				//if ( isset($params->page) ) {
				//	include CSEPATH .'/view/datagrid-fragment.php';
				//} else {
				//	include CSEPATH .'/view/datagrid.php';
				//}
			}
			return;
		}
		
		// default case
		//include CSEPATH .'/view/datagrid.php';
		parent::render();
	}
}
?>